package Verifier.Cryptography;

import Verifier.ByteTree.ByteTree;

public class RandomOracle {
	// the hash function of the RO
	private HashFunction hash;
	// the output length of the RO - this is fixed
	private int outputLength;
	// the PRG used by this RO to generate a fixed length output
	private PRG prg;
	
	
	/**
	 * The constructor of the class.
	 * 
	 * @param hash The Hash Function which will be used in the random oracle.
	 * @param outputLength The length of the output in bits.
	 * */
	public RandomOracle(HashFunction hash, int outputLength){
		// initialize the members
		this.hash=hash;
		this.outputLength = outputLength;
		this.prg = new PRG(hash);
	}
	
	/**
	 * Generates the random oracle
	 * 
	 * @param d The byte tree which the RO will be generated for
	 * 
	 * @return Byte array containing the bit representation of the RO on b.
	 * @throws Exception when no data can be generated by the oracle
	 * */	
	public byte[] OutputRandomOracle(ByteTree d) throws Exception{
		// run the random oracle over the byte tree's byte array representation
		return OutputRandomOracle(d.getByteArray()); 
	}

	
	/**
	 * Generates the random oracle
	 * 
	 * @param ba The byte array which the RO will be generated for.
	 * 
	 * @return Byte array containing the bit representation of the RO on b.
	 * @throws Exception when no data can be generated by the oracle
	 * */
	public byte[] OutputRandomOracle(byte[] ba) throws Exception {
		byte[] s,a;
		// calculate the hash of the ba and the outputlength
		s = hash.HashDigest(concat(ba,outputLength));
		// use the PRG to create an array of bits from the seed wee just calculated
		a = prg.BuildingRandom(s, 0, (int) Math.ceil(outputLength / 8.0)*8 - 1);
		// check that data has returned from the PRG
		if(null == a || a.length == 0){
			throw new Exception("no data to generate by the oracle");
		}
		// clear the first bits, as stated in the verifier's document
		byte maskByte = (byte) ~(0xFF << (8 - outputLength % 8));
		a[0] &= maskByte;
		// return the created byte array
		return a;
	}

	/**
	 * Generates the i-th concatenation.
	 * 
	 * @param byteSeed 	the byte representation of the seed
	 * @param i 		the integer counter
	 * 
	 * @return		byte array containing the param which should be hashed
	 * */
	private byte[] concat(byte[] byteSeed, int i) {
		// create the i-th seed for the RO
		// create an array of bytes for the number i
		byte[] iByteRep = getBytes(i);
		// now, concatenate the arrays which hold the seed and the representation of i
		byte[] resByteArr = new byte[byteSeed.length+iByteRep.length];
		
		System.arraycopy(iByteRep, 0, resByteArr, 0,iByteRep.length);
		System.arraycopy(byteSeed, 0, resByteArr, iByteRep.length, byteSeed.length);
		// return the concatenated array
		return resByteArr;
	}
	
	/**
	 * Generates the byte representation of an integer
	 * 
	 * @param i The integer we  want the byte representation of
	 * 
	 * */
	private byte[] getBytes(int value) {
		// converts an int into an array of bytes
		byte[] b = new byte[4];
		for (int j = 0; j < 4; j++) {
			int offset = (b.length - 1 - j) * 8;
			b[j] = (byte) ((value >>> offset) & 0xFF);
		}
		return b;
	}

}
